package com.erbantou.filemanage.controller.api;

import com.erbantou.filemanage.dto.MirrorCleanRequest;
import com.erbantou.filemanage.service.ResourceService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * ResourceApiController 提供了 RESTful API 接口，用于文件资源的管理操作
 * 包括列出资源、上传文件夹、下载文件夹以及删除资源等功能
 */
@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/api/resources")
public class ResourceApiController {

    private final ResourceService resourceService;

    /**
     * 列出所有资源对象
     *
     * @return 包含资源对象名称的 ResponseEntity
     */
    @GetMapping
    public ResponseEntity<?> listResources() {
        try {
            // 调用 ResourceService 中的方法获取资源对象列表
            List<String> objectNames = resourceService.listResources();
            return ResponseEntity.ok(objectNames);
        } catch (Exception e) {
            log.error("无法列出资源", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("无法列出资源");
        }
    }

    /**
     * 上传文件夹及其包含的文件
     *
     * @param folderName     文件夹名称
     * @param files          包含在文件夹中的文件列表
     * @param requestId      请求 ID
     * @param removeOldFiles 是否删除旧文件，默认为 false
     * @return 包含上传状态的 ResponseEntity
     */
    @PostMapping("/uploadFolder")
    public ResponseEntity<?> uploadFolder(
            @RequestParam(value = "folderName", required = false, defaultValue = "") String folderName,
            @RequestParam("files") List<MultipartFile> files,
            @RequestParam("requestId") String requestId,
            @RequestParam(value = "removeOldFiles", required = false) Boolean removeOldFiles) {
        try {
            // 调用 ResourceService 中的方法上传文件夹
            resourceService.uploadFolder(folderName, files, requestId, removeOldFiles);
            return ResponseEntity.status(HttpStatus.CREATED).body("文件夹上传成功");
        } catch (IllegalArgumentException e) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
        } catch (Exception e) {
            log.error("上传文件夹失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传文件夹失败：" + e.getMessage());
        }
    }

    /**
     * 获取文件 hash
     * 如果文件存在于 Minio，获取缓存的文件哈希值信息
     * 文件不存在则返回 null
     *
     * @param objectName 文件名称
     * @return 文件哈希值信息，如果文件不存在则返回 null
     */
    @GetMapping("/hash")
    public ResponseEntity<String> getFileHash(@RequestParam("objectName") String objectName) {
        String fileHash = resourceService.getFileHashIfExist(objectName);
        log.debug("Get file hash for {}\nfile hash: {}", objectName, fileHash);
        return ResponseEntity.ok(fileHash);
    }

    /**
     * 镜像同步，即保持上传的文件与 Minio 服务器的一致性
     *
     * @param request 包含文件名称列表的请求对象
     * @return 同步结果
     */
    @PostMapping("/mirrorClean")
    public ResponseEntity<String> mirrorSync(@RequestBody MirrorCleanRequest request) {
        return ResponseEntity.ok(resourceService.mirrorClean(request.getObjects()));
    }

    /**
     * 获取目录下文件信息
     * 如果 path 为空，则列出根目录下的文件
     * 否则列出指定目录下的文件
     * 如果 path 以 / 结尾，则列出该目录下的文件
     * 否则列出该文件
     *
     * @param path 资源路径
     * @return 文件列表
     */
    @GetMapping("/list")
    public ResponseEntity<?> getFiles(@RequestParam String path) {
        try {
            if (path == null || path.isEmpty()) {
                path = "";
            }
            List<String> objectNames = resourceService.listResources(path);
            List<FileNode> fileNodes = new ArrayList<>();
            for (String objectName : objectNames) {
                FileNode fileNode = new FileNode(objectName, objectName.endsWith("/"));
                fileNodes.add(fileNode);
            }
            return ResponseEntity.ok(fileNodes);
        } catch (Exception e) {
            log.error("获取目录下文件信息失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("获取目录下文件信息失败：" + e.getMessage());
        }
    }

    /**
     * 删除文件或文件夹
     *
     * @param path 资源路径
     * @return 删除结果消息
     */
    @DeleteMapping("/delete")
    public ResponseEntity<String> deleteFile(@RequestParam String path) {
        try {
            // 权限验证
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth != null && auth.isAuthenticated() && !auth.getPrincipal().equals("anonymousUser")) {
                // 调用 ResourceService 中的方法删除资源对象
                resourceService.deleteResource(path);
                log.info("资源删除成功：" + path);
                return ResponseEntity.ok("资源删除成功");
            } else {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("没有权限删除文件：" + path);
            }
        } catch (Exception e) {
            log.error("资源删除失败：" + path, e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("资源删除失败：" + e.getMessage());
        }
    }

    /**
     * 下载指定路径的文件或文件夹
     *
     * @param path     资源路径
     * @param response HttpServletResponse 对象，用于设置响应属性
     */
    @GetMapping("/download")
    public void downloadResource(@RequestParam String path, HttpServletResponse response) {
        boolean isDirectory = path.endsWith("/");

        if (isDirectory) {
            // 调用 ResourceService 中的方法，获取 ZIP 格式的文件流，下载目录
            String folderName = path.substring(0, path.length() - 1);
            try (InputStream zipStream = resourceService.downloadResource(folderName)) {
                // 对文件夹名称进行 URL 编码
                folderName = folderName.substring(folderName.lastIndexOf("/") + 1);
                String encodedFolderName = UriUtils.encode(folderName, StandardCharsets.UTF_8);
                response.setContentType("application/zip");
                response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFolderName + ".zip\"");
                // 将 ZIP 文件流写入响应输出流
                zipStream.transferTo(response.getOutputStream());
            } catch (Exception e) {
                log.error("下载文件夹失败", e);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                try {
                    response.getWriter().write("处理请求时发生错误：" + e.getMessage());
                } catch (Exception ioException) {
                    log.error("写入响应失败", ioException);
                }
            }
        } else {
            // 调用 ResourceService 中的方法，获取文件流，下载单个文件
            try (InputStream fileStream = resourceService.downloadFile(path)) {
                response.setContentType("application/octet-stream");
                String fileName = path.substring(path.lastIndexOf("/") + 1);
                fileName = UriUtils.encode(fileName, StandardCharsets.UTF_8);
                response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
                fileStream.transferTo(response.getOutputStream());
            } catch (Exception e) {
                log.error("下载文件失败", e);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                try {
                    response.getWriter().write("处理请求时发生错误：" + e.getMessage());
                } catch (Exception ioException) {
                    log.error("写入响应失败", ioException);
                }
            }
        }
    }

    /**
     * 文件节点类，用于封装文件信息，包括文件路径和是否为目录
     */
    record FileNode(String fullPath, boolean directory) {
    }
}
